View the HTML here: https://raw.githack.com/jess-devine/BIO4000W_GIS/refs/heads/main/README.html
# Is Southern Afrotemperate Forest Expanding in the Cape Peninsula?
Investigating whether pioneer forest species indicate forest
expansion, using GIS and species occurrence data to assess forest
dynamics.
Species that are Southern Afrotemperate Forest pioneers:
Virgilia oroboides (Cape Keurboom), Virgilia
divaricata (Garden Route Keurboom), Rapanea melanophloeos
(Cape Beech), and Kiggelaria africana (Wild Peach).
Observation data from iNaturalist and City of Cape Town vegetation
layers will be used.
## Linking to GEOS 3.12.1, GDAL 3.8.4, PROJ 9.3.1; sf_use_s2() is TRUE
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 3.5.1 ✔ tibble 3.2.1
## ✔ lubridate 1.9.3 ✔ tidyr 1.3.1
## ✔ purrr 1.0.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
##
## Attaching package: 'gridExtra'
##
##
## The following object is masked from 'package:dplyr':
##
## combine
##
##
##
## Attaching package: 'cowplot'
##
##
## The following object is masked from 'package:patchwork':
##
## align_plots
##
##
## The following object is masked from 'package:lubridate':
##
## stamp
##
##
## Loading required package: viridisLite
species_list <- c("Virgilia oroboides", # I originally looked at Virgilia divaricata but there were very few observations.
"Rapanea melanophloeos",
"Kiggelaria africana") # credit to Stuart Heath for the suggestion
species_plots <- list()
species_data <- list()
# Get vegetation remnants data
vegr <- st_read("data/cape_peninsula/cape_peninsula/veg/Vegetation_Indigenous_Remnants.shp")
## Reading layer `Vegetation_Indigenous_Remnants' from data source
## `/home/jess/GIT/BIO4000W_GIS/data/cape_peninsula/cape_peninsula/veg/Vegetation_Indigenous_Remnants.shp'
## using driver `ESRI Shapefile'
## Simple feature collection with 3428 features and 7 fields
## Geometry type: POLYGON
## Dimension: XY
## Bounding box: xmin: -63951.23 ymin: -3803532 xmax: 420.7595 ymax: -3705506
## Projected CRS: WGS_1984_Transverse_Mercator
st_write(vegr, "data/cape_peninsula/cape_peninsula/veg/Vegetation_Indigenous_duplicate.shp", append = FALSE)
## Deleting layer `Vegetation_Indigenous_duplicate' using driver `ESRI Shapefile'
## Writing layer `Vegetation_Indigenous_duplicate' to data source
## `data/cape_peninsula/cape_peninsula/veg/Vegetation_Indigenous_duplicate.shp' using driver `ESRI Shapefile'
## Writing 3428 features with 7 fields and geometry type Polygon.
# Crop vegetation remnants data
ext <- c(-66642.18, -3809853.29, -44412.18, -3750723.29)
names(ext) <- c("xmin", "ymin", "xmax", "ymax")
vegr <- st_crop(vegr, ext)
## Warning: attribute variables are assumed to be spatially constant throughout
## all geometries
for (species_name in species_list) {
# Retrieve iNaturalist data
inat_data <- get_inat_obs(taxon_name = species_name,
bounds = c(-35, 18, -33.5, 18.5),
maxresults = 1000)
# Filter iNaturalist data
inat_data <- inat_data %>%
filter(positional_accuracy < 46 &
latitude < 0 &
!is.na(latitude) &
captive_cultivated == "false" &
quality_grade == "research")
inat_data <- st_as_sf(inat_data, coords = c("longitude", "latitude"), crs = 4326) # Convert iNaturalist data to spatial object
inat_data <- st_transform(inat_data, st_crs(vegr)) # Charge projection to match vegetation remnants data
inat_data <- st_intersection(inat_data, vegr) # Remove points in urban areas
# Ensure National_ is a character (fix color mapping issue)
inat_data$National_ <- as.character(inat_data$National_)
# Store in the list
species_data[[species_name]] <- inat_data
# buffer points not in Southern Afrotemprate Forest
nsaf <- inat_data %>%
filter(National_ != "Southern Afrotemperate Forest") %>%
st_buffer(dist = 250)
#Intersect new polygons with veg remnants and filter for those that overlap Southern Afrotemperate Forest only
nsaf <- st_intersection(nsaf, vegr) %>% filter(National_.1 == "Southern Afrotemperate Forest")
# Get count of non-buffered points per vegetation type
no_buffer_summary <- inat_data %>%
st_drop_geometry() %>%
group_by(National_) %>%
summarise(count_no_buffer = n())
# Get count of buffered points per vegetation type
buffer_change_summary <- nsaf %>%
st_drop_geometry() %>%
group_by(National_) %>%
summarise(count_buffer_change = n()) # Renamed to avoid confusion
# Compute total buffered points
total_buffered <- sum(buffer_change_summary$count_buffer_change, na.rm = TRUE)
# Merge summaries
combined_summary <- full_join(no_buffer_summary, buffer_change_summary, by = "National_")
combined_summary <- combined_summary %>%
mutate(count_no_buffer = replace_na(count_no_buffer, 0),
count_buffer_change = replace_na(count_buffer_change, 0))
# Add count_buffer
combined_summary <- combined_summary %>%
mutate(count_buffer = ifelse(National_ == "Southern Afrotemperate Forest",
count_no_buffer + total_buffered, # SAF gets the initial count plus total buffer count
pmax(0,count_no_buffer - count_buffer_change))) # Others get adjusted values
# Rename columns for clarity
combined_summary <- combined_summary %>%
rename(
"Vegetation Type" = National_,
"Initial Count" = count_no_buffer,
"Buffered Change" = count_buffer_change,
"Final Count" = count_buffer
)
# Create the Plot
species_plot <- ggplot() +
annotation_map_tile(type = "osm", progress = "none") +
geom_sf(data = inat_data, aes(color = National_), shape = 16) +
labs(title = paste("Observations of", species_name)) +
theme_minimal()
# Create the Table
table_plot <- tableGrob(combined_summary)
# Print separately
print(species_plot)
grid.newpage()
grid.draw(table_plot)
}
## Warning: attribute variables are assumed to be spatially constant throughout
## all geometries
## Warning: attribute variables are assumed to be spatially constant throughout
## all geometries
## Warning: attribute variables are assumed to be spatially constant throughout
## all geometries
## Warning: attribute variables are assumed to be spatially constant throughout
## all geometries
## Warning: attribute variables are assumed to be spatially constant throughout
## all geometries
## Warning: attribute variables are assumed to be spatially constant throughout
## all geometries
All of the tree species, except Rapanea melanophloeos, had
more observations more than 250m from Southern Afrotemperate Forest than
within this vegetation type. However, the extent of Southern
Afrotemperate Forest is small on the Cape Peninsula.
The majority of species seem to be expanding into Peninsula Sandstone
Fynbos more than other vegetation types. However, Kiggelaria
africana seems to be expanding more into Peninsula Granite
Fynbos.
# Combine all species data into a single dataframe
combined_data <- bind_rows(species_data, .id = "Species")
# Define a color palette for vegetation type
veg_colors <- viridis::viridis(length(unique(combined_data$National_)))
# Define unique shapes for each species
species_shapes <- c("Virgilia oroboides" = 15,
"Virgilia divaricata" = 16,
"Rapanea melanophloeos" = 17,
"Kiggelaria africana" = 18)
# Generate the final plot
ggplot() +
annotation_map_tile(type = "osm", progress = "none") +
geom_sf(data = combined_data,
aes(color = National_, shape = Species),
size = 1.5) +
scale_color_manual(values = veg_colors) + # Assign vegetation colors
scale_shape_manual(values = species_shapes) + # Assign species shapes
labs(title = "All Four Species Observations",
color = "National Vegetation Type",
shape = "Species") +
theme_minimal() +
theme(legend.position = "right")
It’s hard to see what’s going on here so let’s try a interactive
map.
# Ensure data is in WGS84 (longitude-latitude)
combined_data <- combined_data %>% st_transform(4326)
vegr <- st_transform(vegr, 4326)
# Define a color palette for speceis
species_palette <- colorFactor(brewer.pal(min(9, length(unique(combined_data$Species))), "PuOr"), combined_data$Species)
# Define a color palette for vegetation type
veg_palette <- colorFactor(viridis(length(unique(vegr$National_))), vegr$National_)
# Create iNaturalist links for popups
combined_data <- combined_data %>%
mutate(iNat_link = paste0("<a href='https://www.inaturalist.org/observations/", id,
"' target='_blank'>View on iNaturalist</a>"))
# Leaflet Map
leaflet(combined_data) %>%
addTiles() %>%
addPolygons(
data = vegr,
fillColor = ~veg_palette(National_), # Use colorFactor function
color = ~veg_palette(National_),
weight = 1,
fillOpacity = 0.5,
popup = ~paste("<b>Vegetation Type:</b>", National_)
) %>%
addCircleMarkers(
data = combined_data,
lng = ~st_coordinates(geometry)[,1],
lat = ~st_coordinates(geometry)[,2],
radius = 5,
color = ~species_palette(Species), # Use colorFactor function
fillOpacity = 0.9,
popup = ~paste("<b>Species:</b>", Species, "<br>",
"<b>Vegetation Type:</b>", National_, "<br>",
iNat_link)
) %>%
addLegend("bottomright",
pal = species_palette,
values = combined_data$Species,
title = "Species") %>%
addLegend("topright",
pal = veg_palette,
values = vegr$National_,
title = "Vegetation Type")
From these maps, it appears that Southern Afrotemperate Forest may be
expanding in the northern half of the Cape Peninsula. Historical pine
plantations that are now nature reserves in this region may be hotspots
for forest expansion, likely because fire was excluded for long periods
while the plantations were in operation. Additionally, Silvermine Dam
may be facilitating Southern Afrotemperate Forest expansion.
However, some of the observations are in parks, botanical gardens, and
plantations. This likely accounts for the majority of points in Cape
Flats Sand Fynbos. Additionally, it is likely natural for Southern
Afrotemperate Forest to exist around small streams and rivers, but the
vegetation map scale is too coarse to capture these accurately.
knitr::include_graphics("photos/kiggelaria.jpeg")
A resprouting
Kiggelaria africana surrounded by Leucadendron argenteum (Tony
Rebelo)